package hu.mapro.magicroller.core;

import hu.mapro.gamelib.SectionedLinearLevel.Generator;

import com.badlogic.gdx.graphics.Camera;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.graphics.Pixmap.Format;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.PolygonSpriteBatch;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.physics.box2d.Body;
import com.badlogic.gdx.physics.box2d.BodyDef;
import com.badlogic.gdx.physics.box2d.BodyDef.BodyType;
import com.badlogic.gdx.physics.box2d.EdgeShape;
import com.badlogic.gdx.physics.box2d.Fixture;
import com.badlogic.gdx.physics.box2d.FixtureDef;
import com.badlogic.gdx.physics.box2d.World;
import com.badlogic.gdx.utils.Pool;
import com.badlogic.gdx.utils.PooledLinkedList;

class MagicRollerLevelGeneratorBase {
	final MagicRollerConstants constants;

	public MagicRollerLevelGeneratorBase(MagicRollerConstants constants) {
		super();
		this.constants = constants;
	}
}

public class MagicRollerLevelGenerator extends MagicRollerLevelGeneratorBase implements Generator {

	private static final short[] QUAD_TRIANGLES = { 0, 1, 2, 2, 1, 3 };
	
	public static final int P_IMPOSSIBLE = 0;
	public static final int P_DEFAULT = 100;
	
//	public static final float MAX_VERTICAL_UP = MagicRollerPlayer.CAR_HEIGHT*2;
//	public static final float MIN_VERTICAL_UP = MagicRollerPlayer.CAR_HEIGHT;
//	public static final float MAX_VERTICAL_DOWN = MAX_VERTICAL_UP;
//	public static final float MIN_VERTICAL_DOWN = MIN_VERTICAL_UP;
//	public static final float MAX_DIAGONAL_UP = MAX_VERTICAL_UP;
//	public static final float MIN_DIAGONAL_UP = MIN_VERTICAL_UP;
//	public static final float MAX_DIAGONAL_DOWN = MAX_VERTICAL_UP;
//	public static final float MIN_DIAGONAL_DOWN = MIN_VERTICAL_UP;
	
//	public static final float MIN_GAP_WIDTH = MagicRollerPlayer.CAR_WIDTH*2;
//	public static final float MAX_GAP_WIDTH = MagicRollerPlayer.CAR_WIDTH*3;
	
	boolean first = true;

	enum Platform {
		HORIZONTAL {
			@Override
			void init(Stripe stripe) {
				stripe.initHorizontal();
			}
		},
		DIAGONAL {
			@Override
			void init(Stripe stripe) {
				stripe.initDiagonal();
			}
		},
		GAP {
			@Override
			void init(Stripe stripe) {
				stripe.initGap();
			}
			
			@Override
			public void createNextStepWall(Stripe stripe) {
			}
		},
		;

		abstract void init(Stripe stripe);

		public void createNextStepWall(Stripe stripe) {
			stripe.createStepWall();
		}

	}
	
	enum Level {
		ZERO {
			@Override
			void init(Stripe stripe) {
				stripe.initZero();
			}

			@Override
			void initNoGap(Stripe stripe) {
			}
		},
		UP {
			@Override
			void init(Stripe stripe) {
				stripe.initUp();
			}
		},
		DOWN {
			@Override
			void init(Stripe stripe) {
				stripe.initDown();
			}
		},
		;

		abstract void init(Stripe stripe);

		void initNoGap(Stripe stripe) {
			stripe.frontStripePlatformCreateStepWall();
		}
	}
	
	// From Platform, From Level, To Platform, To Level
	private final int[][][][] P = {
			{ // From HORIZONTAL
				{ // From  ZERO
					{ // To HORIZONTAL
						100, // TO ZERO
						constants.P_HORIZONTAL_UP, // TO UP
						100, // TO DOWN
					},
					{ // To DIAGONAL
						P_IMPOSSIBLE, // TO ZERO
						100, // TO UP
						100, // TO DOWN
					},
					{ // To GAP
						constants.P_GAP, // TO ZERO
						constants.P_GAP, // TO UP
						constants.P_GAP, // TO DOWN
					},
				},
				{ // From  UP
					{ // To HORIZONTAL
						100, // TO ZERO
						constants.P_HORIZONTAL_UP, // TO UP
						100, // TO DOWN
					},
					{ // To DIAGONAL
						P_IMPOSSIBLE, // TO ZERO
						100, // TO UP
						100, // TO DOWN
					},
					{ // To GAP
						constants.P_GAP, // TO ZERO
						constants.P_GAP, // TO UP
						constants.P_GAP, // TO DOWN
					},
				},
				{ // From  DOWN
					{ // To HORIZONTAL
						100, // TO ZERO
						constants.P_HORIZONTAL_UP, // TO UP
						100, // TO DOWN
					},
					{ // To DIAGONAL
						P_IMPOSSIBLE, // TO ZERO
						100, // TO UP
						100, // TO DOWN
					},
					{ // To GAP
						constants.P_GAP, // TO ZERO
						constants.P_GAP, // TO UP
						constants.P_GAP, // TO DOWN
					},
				},
			},
			{ // From DIAGONAL
				{ // From  ZERO
					{ // To HORIZONTAL
						100, // TO ZERO
						constants.P_HORIZONTAL_UP, // TO UP
						100, // TO DOWN
					},
					{ // To DIAGONAL
						P_IMPOSSIBLE, // TO ZERO
						100, // TO UP
						100, // TO DOWN
					},
					{ // To GAP
						constants.P_GAP, // TO ZERO
						constants.P_GAP, // TO UP
						constants.P_GAP, // TO DOWN
					},
				},
				{ // From  UP
					{ // To HORIZONTAL
						100, // TO ZERO
						constants.P_HORIZONTAL_UP, // TO UP
						100, // TO DOWN
					},
					{ // To DIAGONAL
						P_IMPOSSIBLE, // TO ZERO
						constants.P_DIAGONAL_UP_DIAGONAL_UP, // TO UP
						100, // TO DOWN
					},
					{ // To GAP
						constants.P_GAP, // TO ZERO
						constants.P_GAP, // TO UP
						constants.P_GAP, // TO DOWN
					},
				},
				{ // From  DOWN
					{ // To HORIZONTAL
						100, // TO ZERO
						constants.P_HORIZONTAL_UP, // TO UP
						100, // TO DOWN
					},
					{ // To DIAGONAL
						P_IMPOSSIBLE, // TO ZERO
						100, // TO UP
						constants.P_DIAGONAL_DOWN_DIAGONAL_DOWN, // TO DOWN
					},
					{ // To GAP
						constants.P_GAP, // TO ZERO
						constants.P_GAP, // TO UP
						constants.P_GAP, // TO DOWN
					},
				},
			},
			{ // From GAP
				{ // From  ZERO
					{ // To HORIZONTAL
						100, // TO ZERO
						constants.P_HORIZONTAL_UP, // TO UP
						100, // TO DOWN
					},
					{ // To DIAGONAL
						P_IMPOSSIBLE, // TO ZERO
						100, // TO UP
						100, // TO DOWN
					},
					{ // To GAP
						P_IMPOSSIBLE, // TO ZERO
						P_IMPOSSIBLE, // TO UP
						P_IMPOSSIBLE, // TO DOWN
					},
				},
				{ // From  UP
					{ // To HORIZONTAL
						100, // TO ZERO
						100, // TO UP
						100, // TO DOWN
					},
					{ // To DIAGONAL
						P_IMPOSSIBLE, // TO ZERO
						100, // TO UP
						100, // TO DOWN
					},
					{ // To GAP
						P_IMPOSSIBLE, // TO ZERO
						P_IMPOSSIBLE, // TO UP
						P_IMPOSSIBLE, // TO DOWN
					},
				},
				{ // From  DOWN
					{ // To HORIZONTAL
						100, // TO ZERO
						constants.P_HORIZONTAL_UP, // TO UP
						100, // TO DOWN
					},
					{ // To DIAGONAL
						P_IMPOSSIBLE, // TO ZERO
						100, // TO UP
						100, // TO DOWN
					},
					{ // To GAP
						P_IMPOSSIBLE, // TO ZERO
						P_IMPOSSIBLE, // TO UP
						P_IMPOSSIBLE, // TO DOWN
					},
				},
			},
	};
	
	private static final StripeType[][][] PR = new StripeType[Platform.values().length][Level.values().length][];
	
	{
		for (Platform fromPlatform : Platform.values()) {
			for (Level fromLevel : Level.values()) {
				int total = 0;

				for (Platform toPlatform : Platform.values()) {
					for (Level toLevel : Level.values()) {
						total += P[fromPlatform.ordinal()][fromLevel.ordinal()][toPlatform.ordinal()][toLevel.ordinal()];
					}
				}

				int idx = 0;
				StripeType[] spr = new StripeType[total];

				for (Platform toPlatform : Platform.values()) {
					for (Level toLevel : Level.values()) {
						StripeType stripeType = new StripeType(toPlatform, toLevel);
						for (int i=0;i<P[fromPlatform.ordinal()][fromLevel.ordinal()][toPlatform.ordinal()][toLevel.ordinal()];i++) {
							spr[idx++] = stripeType;
						}
					}
				}
				
				PR[fromPlatform.ordinal()][fromLevel.ordinal()] = spr;
			}
		}
	}
	
	private static final class StripeType {
		final Platform platform;
		final Level level;
		public StripeType(Platform platform, Level level) {
			super();
			this.platform = platform;
			this.level = level;
		}
	}
	
	final World world;
	final Camera camera;
	final Body buildingsBody;
	final FixtureDef edgeFixtureDef;
	final EdgeShape edgeShape;
	
	final PolygonSpriteBatch polygonSpriteBatch;
	final Texture texture;

	public MagicRollerLevelGenerator(MagicRollerConstants constants, World world, Camera camera) {
		super(constants);
		this.world = world;
		this.camera = camera;
		
		BodyDef buildingsBodyDef = new BodyDef();
		buildingsBodyDef.type = BodyType.StaticBody;
		buildingsBody = world.createBody(buildingsBodyDef);
		
		edgeShape = new EdgeShape();
		
		edgeFixtureDef = new FixtureDef();
		edgeFixtureDef.friction = constants.GROUND_FRICTION;
		edgeFixtureDef.shape = edgeShape;
		
		Pixmap pixmap = new Pixmap(1, 1, Format.RGBA8888);
		pixmap.setColor(Color.GRAY);
		pixmap.drawPixel(0, 0);
		texture = new Texture(pixmap);
		
		polygonSpriteBatch = new PolygonSpriteBatch();
	}
	
	public void dispose() {
		polygonSpriteBatch.dispose();
		texture.dispose();
		edgeShape.dispose();
	}
	
	final Pool<Stripe> stripePool = new Pool<Stripe>() {
		@Override
		protected Stripe newObject() {
			return new Stripe();
		}
	};
	
	Stripe frontStripe = newStripe(); {
		frontStripe.type = new StripeType(Platform.HORIZONTAL, Level.ZERO);
	}
	
	final PooledLinkedList<Stripe> queue = new PooledLinkedList<MagicRollerLevelGenerator.Stripe>(Integer.MAX_VALUE);
	
	final class Stripe {
		StripeType type;
		
		float[] vertices = new float[] {
			0, constants.BOTTOM_Y, Color.GRAY.toFloatBits(), 0, 0,
			0, 0, Color.GRAY.toFloatBits(), 0, 0,
			0, constants.BOTTOM_Y, Color.GRAY.toFloatBits(), 0, 0,
			0, 0, Color.GRAY.toFloatBits(), 0, 0,
		};
		
		Fixture stepFixture;
		Fixture stripeFixture;
		
		float w = 0;
		float platformY2 = 0;
		float nextPlatformX1 = 0;
		float nextPlatformY1 = 0;
		
		void init() {
			w = MathUtils.random(constants.MIN_STRIPE_WIDTH, constants.MAX_STRIPE_WIDTH);
			
			initFixedWidth();
		}

		public void frontStripePlatformCreateStepWall() {
			frontStripe.type.platform.createNextStepWall(this);
		}

		void initFixedWidth() {
			stepFixture = null;
			stripeFixture = null;
			
			nextPlatformX1 = frontStripe.nextPlatformX1 + w;

			type.level.init(this);
			type.platform.init(this);
			
			queue.add(this);
			frontStripe = this;
		}
		
		void initUp() {
			nextPlatformY1 = frontStripe.nextPlatformY1 + MathUtils.random(constants.MIN_UP, constants.MAX_UP);
		}

		void initDown() {
			nextPlatformY1 = frontStripe.nextPlatformY1 - MathUtils.random(constants.MIN_DOWN, constants.MAX_DOWN);
		}
		
		void initZero() {
			nextPlatformY1 = frontStripe.nextPlatformY1;
		}

		void initGap() {
			// create left edge
			stepFixture = addEdge(
					frontStripe.nextPlatformX1, 
					frontStripe.platformY2,
					frontStripe.nextPlatformX1, 
					constants.BOTTOM_Y
			);
			
			// create right edge
			stripeFixture = addEdge(
					nextPlatformX1, 
					nextPlatformY1,
					nextPlatformX1, 
					constants.BOTTOM_Y
			); 
		}

		void initHorizontal() {
			platformY2 = frontStripe.nextPlatformY1;
			initNoGap();
		}
		
		void initDiagonal() {
			platformY2 = nextPlatformY1;
			initNoGap();
		}

		void initNoGap() {
			frontStripePlatformCreateStepWall();
			
			// create platform
			stripeFixture = addEdge(
					frontStripe.nextPlatformX1, 
					frontStripe.nextPlatformY1,
					nextPlatformX1, 
					platformY2
			); 
			
			// create polygon
			vertices[0] = vertices[5] = frontStripe.nextPlatformX1;
			vertices[6] = frontStripe.nextPlatformY1;
			vertices[10] = vertices[15] = nextPlatformX1;  
			vertices[16] = platformY2;
		}

		void createStepWall() {
			stepFixture = addEdge(
					frontStripe.nextPlatformX1,
					frontStripe.platformY2,
					frontStripe.nextPlatformX1,
					frontStripe.nextPlatformY1
			);
		}
		
		Fixture addEdge(float x1, float y1, float x2, float y2) {
			edgeShape.set(x1, y1, x2, y2);
			return buildingsBody.createFixture(edgeFixtureDef);
		}

		public void render() {
			polygonSpriteBatch.draw(texture, vertices, 0, vertices.length, QUAD_TRIANGLES, 0, QUAD_TRIANGLES.length);
		}

		public void destroy() {
			if (stepFixture!=null) {
				buildingsBody.destroyFixture(stepFixture);
			}
			if (stripeFixture!=null) {
				buildingsBody.destroyFixture(stripeFixture);
			}
		}

	}
	
	
	@Override
	public float generateNextFrontSection() {
		Stripe stripe = newStripe();
		
		if (first) {
			first = false;
			stripe.type = new StripeType(Platform.HORIZONTAL, Level.ZERO);
			stripe.w = constants.FIRST_PLATFORM_WIDTH;
			stripe.initFixedWidth();
		} else {
			StripeType[] spr = PR[frontStripe.type.platform.ordinal()][frontStripe.type.level.ordinal()];
			do {
				stripe.type = spr[MathUtils.random(spr.length-1)];
			} while (stripe.type.level == Level.DOWN && frontStripe.nextPlatformY1 - constants.MAX_DOWN < constants.LOWEST_PLATFORM);
			stripe.init();
		}
		
		return stripe.w;
	}
	
	private final Stripe newStripe() {
		return stripePool.obtain();
	}
	

	@Override
	public float destroyRearSection() {
		queue.iter();
		Stripe rearStripe = queue.next();
		try {
			rearStripe.destroy();
			queue.remove();
			return rearStripe.w;
		} finally {
			stripePool.free(rearStripe);
		}
	}

	@Override
	public void flush() {
	}

	@Override
	public void render() {
		polygonSpriteBatch.setProjectionMatrix(camera.combined);
		polygonSpriteBatch.begin();
		
		queue.iter();
		Stripe stripe;
		while ((stripe=queue.next())!=null) {
			stripe.render();
		}
		
		polygonSpriteBatch.end();
	}

	
	
}
